package com.easyaop.api;


import com.easyaop.impl.StaticAopTemplateBinder;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;
public class EasyAopFactory {
    private static final Logger logger = LoggerFactory.getLogger("EasyAopFactory");
    static final int UNINITIALIZED = 0;
    static final int ONGOING_INITIALIZATION = 1;
    static final int FAILED_INITIALIZATION = 2;
    static final int SUCCESSFUL_INITIALIZATION = 3;
    static volatile int INITIALIZATION_STATE = UNINITIALIZED;

    private static String STATIC_TEMPLATE_BINDER_PATH = "com/easyaop/impl/StaticAopTemplateBinder.class";

    static final String UNSUCCESSFUL_INIT_MSG = "com.eaayaop.EasyAopFactory in failed state. Original exception was thrown EARLIER";

    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";

    private static final Map<String,EasyAopTemple>  temples = new java.util.HashMap<>();

    private static EasyAopTemple  singleton = null;

    public static EasyAopTemple getEasyAopTemple() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (EasyAopFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    bind();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return singleton;
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        }
        throw new IllegalStateException("Unreachable code");
    }


    public static EasyAopTemple getEasyAopTemple(String name) {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (EasyAopFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    multipleBind();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return temples.get(name);
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        }
        throw new IllegalStateException("Unreachable code");
    }

    private static void bind() {
        try {
            Set<URL> staticEasyAopBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticEasyAopBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticEasyAopBinderPathSet);
            }
            if (staticEasyAopBinderPathSet != null && staticEasyAopBinderPathSet.size() == 1) {
                singleton = StaticAopTemplateBinder.getSingleton();
            } else {
                for (URL url : staticEasyAopBinderPathSet) {
                    EasyAopBinderClassLoader easyAopBinderClassLoader = new EasyAopBinderClassLoader();
                    Class<?> clazz = easyAopBinderClassLoader.findClass(url, "com.easyaop.impl.StaticAopTemplateBinder");

                    Method getTemplateName = clazz.getDeclaredMethod("getTemplateName");
                    String defaultName = System.getProperty("easyaop.template.default.name", "buddy");

                    String templateName = (String)getTemplateName.invoke(null);
                    if (!Objects.equals(templateName, "default") && Objects.equals(templateName, defaultName)) {
                        Method getSingleton = clazz.getDeclaredMethod("getSingleton");
                        singleton = (EasyAopTemple)getSingleton.invoke(null);
                    }

                }
            }

            if (singleton != null) {
                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            }
            if (staticEasyAopBinderPathSet != null && isAmbiguousStaticEasyAopTempleBinderPathSet(staticEasyAopBinderPathSet)) {
                logger.info("Actual binding is of type [" + StaticAopTemplateBinder.getTemplateName() + "]");
            }
            // release all resources in SUBST_FACTORY
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            logger.info("Failed to load class \"com.easyaop.impl.StaticAopTemplateBinder\"." + "msg:" +msg);
            throw ncde;
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("com.easyaop.impl.StaticAopTemplateBinder.getSingleton()")) {
                logger.info("aop-api version (or later) is incompatible with this binding.");
            }
            throw nsme;
        } catch (Exception e) {
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

    private static void multipleBind() {
        try {
            Set<URL> staticEasyAopBinderPathSet = null;
            // skip check under android, see also
            if (!isAndroid()) {
                staticEasyAopBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticEasyAopBinderPathSet);
            }
            for (URL url : staticEasyAopBinderPathSet) {
                EasyAopBinderClassLoader easyAopBinderClassLoader = new EasyAopBinderClassLoader();
                Class<?> clazz = easyAopBinderClassLoader.findClass(url, "com.easyaop.impl.StaticAopTemplateBinder");

                Method getTemplateName = clazz.getDeclaredMethod("getTemplateName");

                String templateName = (String)getTemplateName.invoke(null);
                if (Objects.equals(templateName, "default")) {
                    continue;
                }
                Method getSingleton = clazz.getDeclaredMethod("getSingleton");
                EasyAopTemple singleton = (EasyAopTemple)getSingleton.invoke(null);
                temples.put(templateName , singleton);

            }
            // the next line does the binding
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // release all resources in SUBST_FACTORY
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            logger.info("Failed to load class \"com.easyaop.impl.StaticAopTemplateBinder\"." + "msg:" +msg);
            throw ncde;
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("com.easyaop.impl.StaticAopTemplateBinder.getSingleton()")) {
                logger.info("aop-api version (or later) is incompatible with this binding.");
            }
            throw nsme;
        } catch (Exception e) {
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticEasyAopBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader EasyAopFactoryClassLoader = EasyAopFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (EasyAopFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_TEMPLATE_BINDER_PATH);
            } else {
                paths = EasyAopFactoryClassLoader.getResources(STATIC_TEMPLATE_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticEasyAopBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            logger.info("Error getting resources from path"+ioe.getMessage());
        }
        return staticEasyAopBinderPathSet;
    }


    private static boolean isAndroid() {
        String vendor = safeGetSystemProperty(JAVA_VENDOR_PROPERTY);
        if (vendor == null)
            return false;
        return vendor.toLowerCase().contains("android");
    }

    public static String safeGetSystemProperty(String key) {
        if (key == null)
            throw new IllegalArgumentException("null input");

        String result = null;
        try {
            result = System.getProperty(key);
        } catch (java.lang.SecurityException sm) {
            ; // ignore
        }
        return result;
    }


    private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
        if (isAmbiguousStaticEasyAopTempleBinderPathSet(binderPathSet)) {
            logger.info("Class path contains multiple EasyAopTemple bindings.");
            for (URL path : binderPathSet) {
                logger.info("Found binding in [" + path + "]");
            }
        }
    }

    private static boolean isAmbiguousStaticEasyAopTempleBinderPathSet(Set<URL> binderPathSet) {
        return binderPathSet.size() > 1;
    }

}
